diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 3061d7aad23..71b32919e98 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -29,16 +29,9 @@ ** xref:kotlinlib/publishing.adoc[] // ** xref:kotlinlib/build-examples.adoc[] ** xref:kotlinlib/web-examples.adoc[] -* xref:pythonlib/intro.adoc[] -// Future Additions -// ** xref:pythonlib/module-config.adoc[] -// ** xref:pythonlib/dependencies.adoc[] -// ** xref:pythonlib/testing.adoc[] -// ** xref:pythonlib/linting.adoc[] -// ** xref:pythonlib/publishing.adoc[] -// ** xref:pythonlib/build-examples.adoc[] -// ** xref:pythonlib/web-examples.adoc[] +* xref:pythonlib/intro.adoc[] +** xref:pythonlib/dependencies.adoc[] * (Experimental) Android with Mill ** xref:android/java.adoc[] diff --git a/docs/modules/ROOT/pages/pythonlib/dependencies.adoc b/docs/modules/ROOT/pages/pythonlib/dependencies.adoc new file mode 100644 index 00000000000..79e946ce869 --- /dev/null +++ b/docs/modules/ROOT/pages/pythonlib/dependencies.adoc @@ -0,0 +1,31 @@ += Python Library Dependencies + +include::partial$gtag-config.adoc[] + + +This page goes into more detail about configuring third party dependencies +for `PythonModule`s. + +== Adding Dependencies + +include::partial$example/pythonlib/dependencies/1-pip-deps.adoc[] + +=== Adding Dependencies via requirements.txt files + +include::partial$example/pythonlib/dependencies/2-pip-requirements.adoc[] + +== Unmanaged Wheels + +include::partial$example/pythonlib/dependencies/3-unmanaged-wheels.adoc[] + +== Downloading Unmanaged Wheels + +include::partial$example/pythonlib/dependencies/4-downloading-unmanaged-wheels.adoc[] + +== Using Custom Package Indexes + +include::partial$example/pythonlib/dependencies/5-repository-config.adoc[] + +== Debugging + +include::partial$example/pythonlib/dependencies/6-debugging.adoc[] diff --git a/docs/modules/ROOT/pages/pythonlib/intro.adoc b/docs/modules/ROOT/pages/pythonlib/intro.adoc index 14211b9abed..c6a4a46e8ee 100644 --- a/docs/modules/ROOT/pages/pythonlib/intro.adoc +++ b/docs/modules/ROOT/pages/pythonlib/intro.adoc @@ -19,4 +19,4 @@ include::partial$example/pythonlib/basic/2-custom-build-logic.adoc[] == Multi-Module Project -include::partial$example/pythonlib/basic/3-multi-module.adoc[] \ No newline at end of file +include::partial$example/pythonlib/basic/3-multi-module.adoc[] diff --git a/example/package.mill b/example/package.mill index cf261132b5f..7b43280b76f 100644 --- a/example/package.mill +++ b/example/package.mill @@ -65,7 +65,8 @@ object `package` extends RootModule with Module { object basic extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "basic")) } object pythonlib extends Module { - object basic extends Cross[ExampleCrossModulePython](build.listIn(millSourcePath / "basic")) + object basic extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "basic")) + object dependencies extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "dependencies")) } object cli extends Module{ @@ -99,40 +100,6 @@ object `package` extends RootModule with Module { object typescript extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "typescript")) } - trait ExampleCrossModulePython extends ExampleCrossModuleJava { - override def lineTransform(line: String) ={ - this.millModuleSegments.parts.last match { - case "1-simple" => - val updatedLine = line - .replace("xref:{language-small}lib/web-examples.adoc", "link:") // Need updated link - .replace("xref:{language-small}lib/build-examples.adoc", "link:") // Need updated link - .replace("compile", "typeCheck") - .replace("Scala console", "Python console") - .replace("Ammonite Scala", "Python") - .replace("assembly", "typeCheck") - .replace(s"// $$ mill jar # bundle the classfiles into a jar suitable for publishing", "") - .replace("foo.scalaVersion", "foo.typeCheck") - updatedLine - case "2-custom-build-logic" => - val updatedLine = line - .replace("17", "10") // it's just the change for page count - .replace("`allSourceFiles` (an existing task)", "`allSourceFiles`") - updatedLine - - case "3-multi-module" => - val updatedLine = line - .replace("compiled", "typeChecked") - .replace("compile", "typeCheck") - .replace("...bar.BarTests...simple...", "test_escaping (...test.TestScript...) ... ok") - .replace("...bar.BarTests...escaping...", "test_simple (...test.TestScript...) ... ok") - updatedLine - - case _ => line - - } - } - } - trait ExampleCrossModuleKotlin extends ExampleCrossModuleJava { override def lineTransform(line: String) = this.millModuleSegments.parts.last match { diff --git a/example/pythonlib/basic/1-simple/build.mill b/example/pythonlib/basic/1-simple/build.mill index 0cb7996a028..1c2fc082fe4 100644 --- a/example/pythonlib/basic/1-simple/build.mill +++ b/example/pythonlib/basic/1-simple/build.mill @@ -1,4 +1,3 @@ -//// SNIPPET:BUILD package build import mill._, pythonlib._ @@ -6,7 +5,7 @@ object foo extends PythonModule { def mainScript = Task.Source { millSourcePath / "src" / "foo.py" } - def pythonDeps = Agg("Jinja2==3.1.4") + def pythonDeps = Seq("Jinja2==3.1.4") object test extends PythonTests with TestModule.Unittest @@ -14,7 +13,6 @@ object foo extends PythonModule { // This is a basic Mill build for a single `PythonModule`, with one // dependency and a test suite using the `Unittest` Library. -//// SNIPPET:TREE // // ---- // build.mill @@ -35,7 +33,6 @@ object foo extends PythonModule { // run.dest/ // ... // ---- -//// SNIPPET:DEPENDENCIES // // This example project uses one dependency - https://pypi.org/project/Jinja2/[Jinja2] // for HTML rendering and uses it to wrap a given input string in HTML templates with proper escaping. diff --git a/example/pythonlib/basic/2-custom-build-logic/build.mill b/example/pythonlib/basic/2-custom-build-logic/build.mill index d8610e9d75c..f66d20d98a0 100644 --- a/example/pythonlib/basic/2-custom-build-logic/build.mill +++ b/example/pythonlib/basic/2-custom-build-logic/build.mill @@ -1,4 +1,3 @@ -//// SNIPPET:BUILD package build import mill._, pythonlib._ diff --git a/example/pythonlib/basic/3-multi-module/build.mill b/example/pythonlib/basic/3-multi-module/build.mill index 20fcb619197..ac08fe51f32 100644 --- a/example/pythonlib/basic/3-multi-module/build.mill +++ b/example/pythonlib/basic/3-multi-module/build.mill @@ -1,4 +1,3 @@ -//// SNIPPET:BUILD package build import mill._, pythonlib._ @@ -13,10 +12,9 @@ object foo extends MyModule { object bar extends MyModule { def mainScript = Task.Source { millSourcePath / "src" / "bar.py" } - def pythonDeps = Agg("Jinja2==3.1.4") + def pythonDeps = Seq("Jinja2==3.1.4") } // -//// SNIPPET:TREE // ---- // build.mill // foo/ diff --git a/example/pythonlib/dependencies/1-pip-deps/build.mill b/example/pythonlib/dependencies/1-pip-deps/build.mill new file mode 100644 index 00000000000..368c1f72743 --- /dev/null +++ b/example/pythonlib/dependencies/1-pip-deps/build.mill @@ -0,0 +1,22 @@ +package build +import mill._, pythonlib._ + +object `package` extends RootModule with PythonModule { + def pythonDeps = Seq( + "numpy==2.1.2", + "pandas~=2.2.3", + "jinja2 @ https://github.com/pallets/jinja/releases/download/3.1.4/jinja2-3.1.4-py3-none-any.whl" + ) +} + +// You can define the `pythonDeps` field to add dependencies to your module, which will be installed +// via https://pip.pypa.io/en/stable/[pip]. Dependencies can include +// https://peps.python.org/pep-0440/[anything that pip understands], such as `==` +// constraints, or even direct references to wheels. + +/** Usage + +> ./mill run +[10 20 30 40 50] + +*/ diff --git a/example/pythonlib/dependencies/1-pip-deps/src/main.py b/example/pythonlib/dependencies/1-pip-deps/src/main.py new file mode 100644 index 00000000000..89ef4627b7d --- /dev/null +++ b/example/pythonlib/dependencies/1-pip-deps/src/main.py @@ -0,0 +1,4 @@ +import numpy as np + +data = np.array([10, 20, 30, 40, 50]) +print(data) diff --git a/example/pythonlib/dependencies/2-pip-requirements/build.mill b/example/pythonlib/dependencies/2-pip-requirements/build.mill new file mode 100644 index 00000000000..21c313ccaea --- /dev/null +++ b/example/pythonlib/dependencies/2-pip-requirements/build.mill @@ -0,0 +1,18 @@ +// You can also read dependencies from `requirements.txt` files. This can be +// useful if you're migrating an existing project to mill. + +package build +import mill._, pythonlib._ + +object `package` extends RootModule with PythonModule { + def pythonRequirementFiles = Task.Sources { + millSourcePath / "requirements.txt" + } +} + +/** Usage + +> ./mill run +[10 20 30 40 50] + +*/ diff --git a/example/pythonlib/dependencies/2-pip-requirements/requirements.txt b/example/pythonlib/dependencies/2-pip-requirements/requirements.txt new file mode 100644 index 00000000000..abdc9235ebe --- /dev/null +++ b/example/pythonlib/dependencies/2-pip-requirements/requirements.txt @@ -0,0 +1 @@ +numpy==2.1.2 diff --git a/example/pythonlib/dependencies/2-pip-requirements/src/main.py b/example/pythonlib/dependencies/2-pip-requirements/src/main.py new file mode 100644 index 00000000000..89ef4627b7d --- /dev/null +++ b/example/pythonlib/dependencies/2-pip-requirements/src/main.py @@ -0,0 +1,4 @@ +import numpy as np + +data = np.array([10, 20, 30, 40, 50]) +print(data) diff --git a/example/pythonlib/dependencies/3-unmanaged-wheels/build.mill b/example/pythonlib/dependencies/3-unmanaged-wheels/build.mill new file mode 100644 index 00000000000..62366cd7b55 --- /dev/null +++ b/example/pythonlib/dependencies/3-unmanaged-wheels/build.mill @@ -0,0 +1,25 @@ +// In most scenarios you should rely on `pythonDeps`/`moduleDeps` and let Mill +// manage the downloading and caching of wheels for you. But in the rare case +// you receive a wheel or folder-full-of-wheels from somewhere and need to +// include it in your project, `unmanagedWheels` is the way to do it. + +package build +import mill._, pythonlib._ + +object `package` extends RootModule with PythonModule { + def unmanagedWheels: T[Seq[PathRef]] = Task.Input { + Seq.from(os.list(millSourcePath / "lib").map(PathRef(_))) + } +} + +// You can override `unmanagedWheels` to point it at a wheel (.whl file) or +// source distribution (.tar.gz with a pyproject.toml file) you place on the +// filesystem, e.g. in the above snippet any files that happen to live in the +// `lib/` folder. + +/** Usage + +> ./mill run +b'"Hello, world!"' + +*/ diff --git a/example/pythonlib/dependencies/3-unmanaged-wheels/lib/jinja2-3.1.4-py3-none-any.whl b/example/pythonlib/dependencies/3-unmanaged-wheels/lib/jinja2-3.1.4-py3-none-any.whl new file mode 100644 index 00000000000..fa6a14bfaa5 Binary files /dev/null and b/example/pythonlib/dependencies/3-unmanaged-wheels/lib/jinja2-3.1.4-py3-none-any.whl differ diff --git a/example/pythonlib/dependencies/3-unmanaged-wheels/lib/orjson-3.10.12.tar.gz b/example/pythonlib/dependencies/3-unmanaged-wheels/lib/orjson-3.10.12.tar.gz new file mode 100644 index 00000000000..56559e2759b Binary files /dev/null and b/example/pythonlib/dependencies/3-unmanaged-wheels/lib/orjson-3.10.12.tar.gz differ diff --git a/example/pythonlib/dependencies/3-unmanaged-wheels/src/main.py b/example/pythonlib/dependencies/3-unmanaged-wheels/src/main.py new file mode 100644 index 00000000000..003e7655ae0 --- /dev/null +++ b/example/pythonlib/dependencies/3-unmanaged-wheels/src/main.py @@ -0,0 +1,9 @@ +# this comes from a wheel +import jinja2 + +# this comes from an sdist +import orjson as oj + +environment = jinja2.Environment() +template = environment.from_string("Hello, {{ name }}!") +print(oj.dumps(template.render(name="world"))) diff --git a/example/pythonlib/dependencies/4-downloading-unmanaged-wheels/build.mill b/example/pythonlib/dependencies/4-downloading-unmanaged-wheels/build.mill new file mode 100644 index 00000000000..5eb12d24ad0 --- /dev/null +++ b/example/pythonlib/dependencies/4-downloading-unmanaged-wheels/build.mill @@ -0,0 +1,37 @@ +// You can also override `unmanagedWheels` to point it at wheels that you want to +// download from arbitrary URLs. +// `requests.get` comes from the https://github.com/com-lihaoyi/requests-scala[Requests-Scala] +// library, one of Mill's xref:fundamentals/bundled-libraries.adoc[Bundled Libraries]. +// +package build +import mill._, pythonlib._ + +object `package` extends RootModule with PythonModule { + def unmanagedWheels = Task { + val name = "jinja2-3.1.4-py3-none-any.whl" + val url = s"https://github.com/pallets/jinja/releases/download/3.1.4/$name" + os.write(Task.dest / name, requests.get.stream(url)) + Seq(PathRef(Task.dest / name)) + } +} + +/** Usage + +> ./mill run +Hello, world! + +*/ + +// Tasks like `unmanagedWheels` and `pythonDeps` are cached, so your wheel is downloaded only +// once and re-used indefinitely after that. This is usually not a problem, because usually URLs +// follow the rule that https://www.w3.org/Provider/Style/URI[Cool URIs don't change], and so files +// downloaded from the same URL will always contain the same contents. +// +// NOTE: An unmanaged wheel downloaded via `requests.get` is still unmanaged: even though you +// downloaded it from somewhere, `requests.get` does not know how to pull in third party +// dependencies or de-duplicate different versions on the classpath. All the same caveats you need +// to worry about when dealing with xref:#_unmanaged_wheels[unmanaged wheels] apply here as well. In +// case you **do** want mill to take care of managing dependencies of a package which is not +// available on PyPI, you shouldn't get that package in `unmanagedWheels` (like we did in the +// example above). Instead, you can declare the dependency as a regular `pythonDep` +// https://peps.python.org/pep-0440/#direct-references[as a direct URL that pip understands]. diff --git a/example/pythonlib/dependencies/4-downloading-unmanaged-wheels/src/main.py b/example/pythonlib/dependencies/4-downloading-unmanaged-wheels/src/main.py new file mode 100644 index 00000000000..2d39ce8117c --- /dev/null +++ b/example/pythonlib/dependencies/4-downloading-unmanaged-wheels/src/main.py @@ -0,0 +1,4 @@ +import jinja2 +environment = jinja2.Environment() +template = environment.from_string("Hello, {{ name }}!") +print(template.render(name="world")) diff --git a/example/pythonlib/dependencies/5-repository-config/build.mill b/example/pythonlib/dependencies/5-repository-config/build.mill new file mode 100644 index 00000000000..bd3771bd315 --- /dev/null +++ b/example/pythonlib/dependencies/5-repository-config/build.mill @@ -0,0 +1,44 @@ +// By default, dependencies are resolved from https://pypi.org/[the Python +// Package Index (PyPI)], the standard package index for python projects. You +// can also add your own package indexes by overriding the `indexes` task in +// the module: + +package build +import mill._, pythonlib._ + +object foo extends PythonModule { + + def pythonDeps = Seq( + "testpkg-jodersky==0.0.1" // a test package, only available on test.pypi.org + ) + + // override this task to add or replace the package indexes + def indexes = super.indexes() ++ Seq("https://test.pypi.org/simple/") +} + +// Mill uses https://pip.pypa.io/en/stable/[pip] to find and install dependencies. +// +// You can configure pip through its +// https://pip.pypa.io/en/stable/topics/configuration/#location[normal configuration files.] +// +// === Private indexes +// +// You can read up in more detail on https://pip.pypa.io/en/stable/topics/authentication/[how to +// configure pip to authenticate to private indexes]. Here is an example which reads a package from +// an environment variable: + +object bar extends PythonModule { + def indexPassword = Task.Input { Task.env.apply("COMPANY_PASSWORD") } + def indexes = Task { + Seq(s"https://username:${indexPassword()}@pypi.company.com/simple") + } +} + +// More advanced authentication techniques are available by configuring pip directly. + +/** Usage + +> ./mill foo.run +2 + +*/ diff --git a/example/pythonlib/dependencies/5-repository-config/foo/src/main.py b/example/pythonlib/dependencies/5-repository-config/foo/src/main.py new file mode 100644 index 00000000000..877bc11ad2e --- /dev/null +++ b/example/pythonlib/dependencies/5-repository-config/foo/src/main.py @@ -0,0 +1,3 @@ +from testpkg_jodersky import example + +print(example.add_one(1)) diff --git a/example/pythonlib/dependencies/6-debugging/build.mill b/example/pythonlib/dependencies/6-debugging/build.mill new file mode 100644 index 00000000000..b70c66e1c68 --- /dev/null +++ b/example/pythonlib/dependencies/6-debugging/build.mill @@ -0,0 +1,34 @@ +// In case anything goes wrong, or if you're just curious, you can see what +// arguments mill passes to `pip install` by looking at the output of the +// `pipInstallArgs` task. + +package build +import mill._, pythonlib._ + +object `package` extends RootModule with PythonModule { + def pythonDeps = Seq( + "numpy==2.1.2", + "pandas~=2.2.3", + "jinja2 @ https://github.com/pallets/jinja/releases/download/3.1.4/jinja2-3.1.4-py3-none-any.whl" + ) + + def indexes = Seq("invalid_index") +} + +/** Usage + +> ./mill show pipInstallArgs +{ + "args": [ + "--index-url", + "invalid_index", + "mypy==1.13.0", + "pex==2.24.1", + "numpy==2.1.2", + "pandas~=2.2.3", + "jinja2 @ https://github.com/pallets/jinja/releases/download/3.1.4/jinja2-3.1.4-py3-none-any.whl" + ], + "sig": ... +} + +*/ diff --git a/pythonlib/src/mill/pythonlib/PipModule.scala b/pythonlib/src/mill/pythonlib/PipModule.scala index ffe27521221..be7918e2838 100644 --- a/pythonlib/src/mill/pythonlib/PipModule.scala +++ b/pythonlib/src/mill/pythonlib/PipModule.scala @@ -22,13 +22,13 @@ trait PipModule extends Module { * * Dependencies declared here will also be required when installing this module. */ - def pythonDeps: T[Agg[String]] = Task { Agg.empty[String] } + def pythonDeps: T[Seq[String]] = Task { Seq.empty[String] } /** * Python dependencies of this module, and all other modules that this module * depends on, recursively. */ - def transitivePythonDeps: T[Agg[String]] = Task { + def transitivePythonDeps: T[Seq[String]] = Task { val upstreamDependencies = Task.traverse(moduleDeps)(_.transitivePythonDeps)().flatten pythonDeps() ++ upstreamDependencies } @@ -40,36 +40,51 @@ trait PipModule extends Module { * * @see [[pythonDeps]] */ - def pythonRequirements: T[Seq[PathRef]] = Task { Seq.empty[PathRef] } + def pythonRequirementFiles: T[Seq[PathRef]] = Task { Seq.empty[PathRef] } /** * requirements.txt of this module, and all other modules that this module * depends on, recursively. * - * @see pythonRequirements + * @see [[pythonRequirementFiles]] */ - def transitivePythonRequirements: T[Agg[PathRef]] = Task { - val upstream = Task.traverse(moduleDeps)(_.transitivePythonRequirements)().flatten - pythonRequirements() ++ upstream + def transitivePythonRequirementFiles: T[Seq[PathRef]] = Task { + val upstream = Task.traverse(moduleDeps)(_.transitivePythonRequirementFiles)().flatten + pythonRequirementFiles() ++ upstream } /** - * Any python dependencies for development you want to add to this module. + * Any python dependencies for development tools you want to add to this module. * - * These dependencies are similar to `pythonDeps`, but will not be required to - * install this module, only to work on it. For example, type checkers, - * linters, and bundlers should be declared here. + * These dependencies are similar to `pythonDeps`, but will not be required to install this + * module, only to work on it. For example, type checkers, linters, and bundlers should be + * declared here. * * @see [[pythonDeps]] */ - def pythonDevDeps: T[Agg[String]] = Task { Agg.empty[String] } + def pythonToolDeps: T[Seq[String]] = Task { Seq.empty[String] } /** - * Python dependencies for development declared in `requirements.txt` files. + * Any python wheels to install directly. * - * @see [[pythonDevDeps]] + * Note: you can also include wheels by using [direct + * references](https://peps.python.org/pep-0440/#direct-references) in [[pythonRequirementFiles]], for + * example `"pip @ file:///localbuilds/pip-1.3.1-py33-none-any.whl"`. However, if you do that then + * changes to these files won't get picked up and you are on the hook for cache invalidation. + * Therefore, if you have any wheels that you wish to install directly, it is recommended to add + * them here. */ - def pythonDevRequirements: T[Seq[PathRef]] = Task { Seq.empty[PathRef] } + def unmanagedWheels: T[Seq[PathRef]] = Task { Seq.empty[PathRef] } + + /** + * Any python wheels to install directly, for this module and all upstream modules, recursively. + * + * @see [[unmanagedWheels]] + */ + def transitiveUnmanagedWheels: T[Seq[PathRef]] = Task { + val upstream = Task.traverse(moduleDeps)(_.transitiveUnmanagedWheels)().flatten + unmanagedWheels() ++ upstream + } /** * Base URLs of the Python Package Indexes to search for packages. Defaults to @@ -78,7 +93,7 @@ trait PipModule extends Module { * These should point to repositories compliant with PEP 503 (the simple * repository API) or local directories laid out in the same format. */ - def indexes: T[Agg[String]] = Task { + def indexes: T[Seq[String]] = Task { Seq("https://pypi.org/simple") } @@ -92,9 +107,10 @@ trait PipModule extends Module { * * @see [[indexes]] * @see [[pythonDeps]] - * @see [[pythonDevDeps]] + * @see [[unmanagedWheels]] + * @see [[pythonToolDeps]] */ - def pipInstallArgs: T[Seq[String]] = Task { + def pipInstallArgs: T[PipModule.InstallArgs] = Task { val indexArgs: Seq[String] = indexes().toList match { case Nil => Seq("--no-index") case head :: Nil => Seq("--index-url", head) @@ -102,12 +118,49 @@ trait PipModule extends Module { Seq("--index-url", head) ++ tail.flatMap(t => Seq("--extra-index-url", t)) } - indexArgs ++ - pythonDevDeps() ++ - transitivePythonDeps() ++ - (pythonDevRequirements() ++ transitivePythonRequirements()).flatMap(pr => - Seq("-r", pr.path.toString) - ) + PipModule.InstallArgs( + indexArgs ++ + transitiveUnmanagedWheels().map(_.path.toString) ++ + pythonToolDeps() ++ + transitivePythonDeps() ++ + transitivePythonRequirementFiles().flatMap(pr => + Seq("-r", pr.path.toString) + ), + transitiveUnmanagedWheels() ++ transitivePythonRequirementFiles() + ) + } + +} + +object PipModule { + + /** + * A list of string arguments, with a cache-busting signature for any strings which represent + * files. + */ + case class InstallArgs( + args: Seq[String], + sig: Int + ) + object InstallArgs { + implicit val rw: upickle.default.ReadWriter[InstallArgs] = upickle.default.macroRW + def apply( + args: Seq[String], + paths: Seq[PathRef] + ): InstallArgs = { + val hash = java.security.MessageDigest.getInstance("MD5") + for (arg <- args) { + hash.update(arg.getBytes("utf-8")) + } + for (path <- paths) { + hash.update((path.sig >> 24).toByte) + hash.update((path.sig >> 16).toByte) + hash.update((path.sig >> 8).toByte) + hash.update((path.sig).toByte) + } + + InstallArgs(args.toSeq, java.util.Arrays.hashCode(hash.digest())) + } } } diff --git a/pythonlib/src/mill/pythonlib/PythonModule.scala b/pythonlib/src/mill/pythonlib/PythonModule.scala index d2e782c5cdc..219673a3254 100644 --- a/pythonlib/src/mill/pythonlib/PythonModule.scala +++ b/pythonlib/src/mill/pythonlib/PythonModule.scala @@ -44,7 +44,7 @@ trait PythonModule extends PipModule with TaskModule { outer => def pythonExe: T[PathRef] = Task { os.call((hostPythonCommand(), "-m", "venv", Task.dest / "venv")) val python = Task.dest / "venv" / "bin" / "python3" - os.call((python, "-m", "pip", "install", pipInstallArgs()), stdout = os.Inherit) + os.call((python, "-m", "pip", "install", pipInstallArgs().args), stdout = os.Inherit) PathRef(python) } @@ -61,12 +61,12 @@ trait PythonModule extends PipModule with TaskModule { outer => def resources: T[Seq[PathRef]] = Task.Sources { millSourcePath / "resources" } /** - * The mainScript to run. This file may not exist if this module is only a library. + * The python script to run. This file may not exist if this module is only a library. */ def mainScript: T[PathRef] = Task.Source { millSourcePath / "src" / "main.py" } - override def pythonDevDeps: T[Agg[String]] = Task { - super.pythonDevDeps() ++ Agg("mypy==1.13.0", "pex==2.24.1") + override def pythonToolDeps: T[Seq[String]] = Task { + super.pythonToolDeps() ++ Seq("mypy==1.13.0", "pex==2.24.1") } /** @@ -75,15 +75,6 @@ trait PythonModule extends PipModule with TaskModule { outer => */ def unmanagedPythonPath: T[Seq[PathRef]] = Task { Seq.empty[PathRef] } - /** - * Source directories of this module, and all other modules that this module - * depends on, recursively. - */ - def transitiveSources: T[Seq[PathRef]] = Task { - val upstreamSources = Task.traverse(moduleDeps)(_.transitiveSources)().flatten - sources() ++ upstreamSources - } - /** * The directories used to construct the PYTHONPATH for this module, used for * execution, excluding upstream modules. @@ -92,7 +83,7 @@ trait PythonModule extends PipModule with TaskModule { outer => * directories. */ def localPythonPath: T[Seq[PathRef]] = Task { - transitiveSources() ++ unmanagedPythonPath() ++ resources() + sources() ++ resources() ++ unmanagedPythonPath() } /** @@ -136,7 +127,7 @@ trait PythonModule extends PipModule with TaskModule { outer => } /** - * Run the main python mainScript of this module. + * Run the main python script of this module. * * @see [[mainScript]] */ diff --git a/pythonlib/src/mill/pythonlib/TestModule.scala b/pythonlib/src/mill/pythonlib/TestModule.scala index d1af07fd92f..f337cecc709 100644 --- a/pythonlib/src/mill/pythonlib/TestModule.scala +++ b/pythonlib/src/mill/pythonlib/TestModule.scala @@ -1,6 +1,5 @@ package mill.pythonlib -import mill.Agg import mill.Task import mill.Command import mill.TaskModule @@ -66,8 +65,8 @@ object TestModule { /** TestModule that uses pytest to run tests. */ trait Pytest extends PythonModule with TestModule { - override def pythonDeps: T[Agg[String]] = T { - super.pythonDeps() ++ Agg("pytest==8.3.3") + override def pythonDeps: T[Seq[String]] = T { + super.pythonDeps() ++ Seq("pytest==8.3.3") } protected def testTask(args: Task[Seq[String]]) = Task.Anon { diff --git a/pythonlib/test/src/mill/pythonlib/HelloWorldTests.scala b/pythonlib/test/src/mill/pythonlib/HelloWorldTests.scala index 8193b759c3d..33b8deafa9b 100644 --- a/pythonlib/test/src/mill/pythonlib/HelloWorldTests.scala +++ b/pythonlib/test/src/mill/pythonlib/HelloWorldTests.scala @@ -30,8 +30,6 @@ object HelloWorldTests extends TestSuite { val Right(result) = eval.apply(HelloWorldPython.qux.run(Args())) assert(baos.toString().contains("Hello, Qux!\n")) - // may need future update - // assert(baos.toString() == "Hello, Qux!\n") } test("test") {