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