source: java2python/src/main/java/tudelft/utilities/j2p/PyProgram.java@ 399

Last change on this file since 399 was 399, checked in by wouter, 2 years ago

#115 let getInstalls return Set to avoid many duplicates. Also fixed PyProgrem correspondingly

File size: 5.2 KB
Line 
1package tudelft.utilities.j2p;
2
3import java.io.BufferedOutputStream;
4import java.io.File;
5import java.io.FileNotFoundException;
6import java.io.FileOutputStream;
7import java.io.FileWriter;
8import java.io.IOException;
9import java.nio.file.Files;
10import java.nio.file.Path;
11import java.util.Arrays;
12import java.util.HashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Set;
16
17import tudelft.utilities.pyrunner.Call;
18import tudelft.utilities.pyrunner.PythonError;
19import tudelft.utilities.pyrunner.PythonVenv;
20
21/**
22 * Contains a fully runnable python program. This contains multiple
23 * {@link PyModule}s.
24 *
25 */
26public class PyProgram {
27 private final List<PyModule> modules;
28
29 public PyProgram(List<PyModule> modules) {
30 this.modules = modules;
31 }
32
33 /**
34 *
35 * /**
36 *
37 * @param javaBaseDir the java base dir from where packages are found,
38 * typically src/main/java.
39 * @param modulenames list of full.qualified.path.java list of java programs
40 * inside the base dir. Each java source file of the
41 * program is relative to the javaBaseDir. This
42 * relativeness allows us to derive the correct filename,
43 * class path etc, without requesting an actual class
44 * object.
45 * @return PyProgram containing the translated java program including all
46 * specified packages
47 * @throws FileNotFoundException if file can't be found
48 */
49
50 public static PyProgram fromModules(Path javaBaseDir, List<File> modules)
51 throws FileNotFoundException {
52 List<PyModule> pymodules = new LinkedList<>();
53
54 for (File module : modules) {
55 pymodules.add(PyModule.fromJavaFile(javaBaseDir, module));
56 }
57 return new PyProgram(pymodules);
58
59 }
60
61 /**
62 *
63 * @param pycode the python code to be zipped as pip-installable zip. It
64 * is assumed that the code is just 1 file.
65 * @param modulename The name of the module. It is assumed there is just 1
66 * module, and this module is also a directory in the root
67 * folder, and only 1 level deep. The main program must be
68 * in this module. FIXME should be in pycode.
69 * @return a zip {@link File} containing all the code, as a python tar.gz
70 * package that can be pip install'ed . For instance like this:
71 *
72 * <pre><!-- @formatter:off -->
73 * PythonVenv venv = new PythonVenv(zipfile, reporter);
74 * String res = venv.call(Arrays.asList("-m", "testcode"), 10000);
75 </pre><!-- @formatter:on -->
76 * @throws IOException
77 * @throws PythonError if python code to build tar.gz fails for
78 * some reason
79 * @throws InterruptedException
80 */
81 public File getZip() throws IOException, InterruptedException, PythonError {
82 // HACK we need to find good name and properly enumerate ALL modules,
83 // not just first one.
84 String modulename = modules.get(0).getSourceFile().toString()
85 .split("/")[0];
86
87 Path tmppath = Files.createTempDirectory("j2p");
88 System.out.println(tmppath);
89
90 Set<String> installs = new HashSet<String>();
91 List<String> modulenames = new LinkedList<>();
92 // write the translated file
93 for (PyModule pymodule : modules) {
94 String modname = pymodule.getSourceFile().getPath().replace(".java",
95 "");
96 modulenames.add("'" + modname + "'");
97 File f = tmppath.resolve(modname + ".py").toFile();
98 f.getParentFile().mkdirs();// create dirs
99 FileOutputStream fos = new FileOutputStream(f);
100 BufferedOutputStream bos = new BufferedOutputStream(fos);
101 // convert string to byte array
102 String filter = "from " + modname.replace("/", ".") + " .*";
103 byte[] bytes = pymodule.getCode().toCode(filter).getBytes();
104 // write byte array to file
105 bos.write(bytes);
106 bos.close();
107 fos.close();
108 installs.addAll(pymodule.getCode().getInstalls());
109 }
110
111 List<String> requirements = installs.stream()
112 .map(str -> "\"" + str + "\"").toList();
113 // we must provide many nonsense fields like url and author to get this
114 // working...
115 String setuppy = "from setuptools import setup, find_packages\n" + "\n"
116 + "setup(\n" + " name='" + modulename + "',\n"
117 + " version=1,\n" + " author='Whatever',\n"
118 + " author_email='Whatever',\n"
119 + " url='http://nonsense',\n" + " py_modules="
120 + modulenames + ",\n" + " install_requires=" + requirements
121 + ",\n" + "\n" + " classifiers=[\n"
122 + " 'Programming Language :: Python :: 3.8',\n"
123 + " 'Programming Language :: Python :: 3.9'\n"
124 + " ],\n" + ")\n" + "";
125
126 File f = tmppath.resolve("setup.py").toFile();
127 FileWriter fw = new FileWriter(f);
128 fw.write(setuppy);
129 fw.close();
130
131 // without readme, python complains
132 f = tmppath.resolve("README").toFile();
133 fw = new FileWriter(f);
134 fw.write("");
135 fw.close();
136
137 // call python to build the package to be sure we have a correct package
138 new Call(PythonVenv.PYTHON3, Arrays.asList("setup.py", "sdist"), 30000,
139 tmppath.toFile()).run();
140
141 // -1 is the version number added py setup.py to the file
142 return tmppath.resolve("dist/" + modulename + "-1.tar.gz").toFile();
143 }
144
145 @Override
146 public String toString() {
147 return "PyProgram[" + modules + "]";
148 }
149}
Note: See TracBrowser for help on using the repository browser.