diff --git a/example/package.mill b/example/package.mill
index 1616927e8ca..c1fd451dbbf 100644
--- a/example/package.mill
+++ b/example/package.mill
@@ -68,6 +68,7 @@ object `package` extends RootModule with Module {
object basic extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "basic"))
object dependencies extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "dependencies"))
object publishing extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "publishing"))
+ object web extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "web"))
object module extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "module"))
}
diff --git a/example/pythonlib/web/1-hello-flask/build.mill b/example/pythonlib/web/1-hello-flask/build.mill
new file mode 100644
index 00000000000..2fc70becb84
--- /dev/null
+++ b/example/pythonlib/web/1-hello-flask/build.mill
@@ -0,0 +1,32 @@
+package build
+import mill._, pythonlib._
+
+object foo extends PythonModule {
+
+ def mainScript = Task.Source { millSourcePath / "src" / "foo.py" }
+
+ def pythonDeps = Seq("flask==3.1.0")
+
+ object test extends PythonTests with TestModule.Unittest
+
+}
+
+/** Usage
+
+> ./mill foo.test
+...
+test_hello_flask (test.TestScript...)
+Test the '/' endpoint. ... ok
+...
+Ran 1 test...
+OK
+...
+
+> ./mill foo.runBackground
+
+> curl http://localhost:5000
+...
Hello, Mill!
...
+
+> ./mill clean foo.runBackground
+
+*/
diff --git a/example/pythonlib/web/1-hello-flask/foo/src/foo.py b/example/pythonlib/web/1-hello-flask/foo/src/foo.py
new file mode 100644
index 00000000000..ca92ec70e52
--- /dev/null
+++ b/example/pythonlib/web/1-hello-flask/foo/src/foo.py
@@ -0,0 +1,10 @@
+from flask import Flask
+
+app = Flask(__name__)
+
+@app.route("/")
+def hello_world():
+ return "Hello, Mill!
"
+
+if __name__ == '__main__':
+ app.run(debug=True)
\ No newline at end of file
diff --git a/example/pythonlib/web/1-hello-flask/foo/test/src/test.py b/example/pythonlib/web/1-hello-flask/foo/test/src/test.py
new file mode 100644
index 00000000000..982e222e474
--- /dev/null
+++ b/example/pythonlib/web/1-hello-flask/foo/test/src/test.py
@@ -0,0 +1,17 @@
+import unittest
+from foo import app # type: ignore
+
+class TestScript(unittest.TestCase):
+ def setUp(self):
+ """Set up the test client before each test."""
+ self.app = app.test_client() # Initialize the test client
+ self.app.testing = True # Enable testing mode for better error handling
+
+ def test_hello_flask(self):
+ """Test the '/' endpoint."""
+ response = self.app.get('/') # Simulate a GET request to the root endpoint
+ self.assertEqual(response.status_code, 200) # Check the HTTP status code
+ self.assertIn(b"Hello, Mill!", response.data) # Check if the response contains the expected text
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/example/pythonlib/web/2-todo-flask/build.mill b/example/pythonlib/web/2-todo-flask/build.mill
new file mode 100644
index 00000000000..c3523519099
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/build.mill
@@ -0,0 +1,24 @@
+package build
+import mill._, pythonlib._
+
+object todo extends PythonModule {
+
+ def mainScript = Task.Source { millSourcePath / "src" / "app.py" }
+
+ def pythonDeps = Seq("flask==3.1.0", "Flask-SQLAlchemy==3.1.1", "Flask-WTF==1.2.2")
+
+ object test extends PythonTests with TestModule.Unittest
+
+}
+
+// TODO: Testing will be added soon...
+
+/** Usage
+
+> ./mill todo.runBackground
+
+> curl http://localhost:5001
+
+> ./mill clean todo.runBackground
+
+*/
diff --git a/example/pythonlib/web/2-todo-flask/todo/src/app.py b/example/pythonlib/web/2-todo-flask/todo/src/app.py
new file mode 100644
index 00000000000..0cebf1afb94
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/todo/src/app.py
@@ -0,0 +1,74 @@
+from flask import Flask, render_template, redirect, url_for, flash, request
+from flask_sqlalchemy import SQLAlchemy
+from flask_wtf import FlaskForm
+from wtforms import StringField, TextAreaField, SelectField, DateField, SubmitField
+from wtforms.validators import DataRequired, Length
+
+# Initialize Flask App and Database
+app = Flask(__name__, static_folder="../static", template_folder="../templates")
+app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///todo.db"
+app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+app.config["SECRET_KEY"] = "your_secret_key"
+
+# Import models
+from models import Task, db
+
+# Import forms
+from forms import TaskForm
+
+db.init_app(app)
+
+
+# Routes
+@app.route("/")
+def index():
+ tasks = Task.query.all()
+ return render_template("index.html", tasks=tasks)
+
+
+@app.route("/add", methods=["GET", "POST"])
+def add_task():
+ form = TaskForm()
+ if form.validate_on_submit():
+ new_task = Task(
+ title=form.title.data,
+ description=form.description.data,
+ status=form.status.data,
+ deadline=form.deadline.data,
+ )
+ db.session.add(new_task)
+ db.session.commit()
+ flash("Task added successfully!", "success")
+ return redirect(url_for("index"))
+ return render_template("task.html", form=form, title="Add Task")
+
+
+@app.route("/edit/", methods=["GET", "POST"])
+def edit_task(task_id):
+ task = Task.query.get_or_404(task_id)
+ form = TaskForm(obj=task)
+ if form.validate_on_submit():
+ task.title = form.title.data
+ task.description = form.description.data
+ task.status = form.status.data
+ task.deadline = form.deadline.data
+ db.session.commit()
+ flash("Task updated successfully!", "success")
+ return redirect(url_for("index"))
+ return render_template("task.html", form=form, title="Edit Task")
+
+
+@app.route("/delete/")
+def delete_task(task_id):
+ task = Task.query.get_or_404(task_id)
+ db.session.delete(task)
+ db.session.commit()
+ flash("Task deleted successfully!", "success")
+ return redirect(url_for("index"))
+
+
+# Create database tables and run the app
+if __name__ == "__main__":
+ with app.app_context():
+ db.create_all()
+ app.run(debug=True, port=5001)
diff --git a/example/pythonlib/web/2-todo-flask/todo/src/forms.py b/example/pythonlib/web/2-todo-flask/todo/src/forms.py
new file mode 100644
index 00000000000..be10adcff0f
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/todo/src/forms.py
@@ -0,0 +1,10 @@
+from flask_wtf import FlaskForm
+from wtforms import StringField, TextAreaField, SelectField, DateField, SubmitField
+from wtforms.validators import DataRequired, Length
+
+class TaskForm(FlaskForm):
+ title = StringField('Title', validators=[DataRequired(), Length(max=100)])
+ description = TextAreaField('Description')
+ status = SelectField('Status', choices=[('Pending', 'Pending'), ('Completed', 'Completed')])
+ deadline = DateField('Deadline', format='%Y-%m-%d', validators=[DataRequired()])
+ submit = SubmitField('Save')
diff --git a/example/pythonlib/web/2-todo-flask/todo/src/models.py b/example/pythonlib/web/2-todo-flask/todo/src/models.py
new file mode 100644
index 00000000000..93bd7c82bf4
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/todo/src/models.py
@@ -0,0 +1,14 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
+
+class Task(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ title = db.Column(db.String(100), nullable=False)
+ description = db.Column(db.Text, nullable=True)
+ status = db.Column(db.String(20), default='Pending') # Options: Pending, Completed
+ created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
+ deadline = db.Column(db.Date)
+
+ def __repr__(self):
+ return f''
diff --git a/example/pythonlib/web/2-todo-flask/todo/static/style.css b/example/pythonlib/web/2-todo-flask/todo/static/style.css
new file mode 100644
index 00000000000..54f07a91f48
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/todo/static/style.css
@@ -0,0 +1,21 @@
+body {
+ background-color: #f8f9fa;
+}
+
+.navbar-brand {
+ font-weight: bold;
+}
+
+.table th,
+.table td {
+ text-align: center;
+ vertical-align: middle;
+}
+
+.btn {
+ margin-right: 5px;
+}
+
+.container {
+ max-width: 900px;
+}
diff --git a/example/pythonlib/web/2-todo-flask/todo/templates/base.html b/example/pythonlib/web/2-todo-flask/todo/templates/base.html
new file mode 100644
index 00000000000..7beef670609
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/todo/templates/base.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Flask To-Do App
+
+
+
+
+
+
+ {% with messages = get_flashed_messages(with_categories=True) %}
+ {% if messages %}
+ {% for category, message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+ {% endwith %}
+ {% block content %}{% endblock %}
+
+
+
+
diff --git a/example/pythonlib/web/2-todo-flask/todo/templates/index.html b/example/pythonlib/web/2-todo-flask/todo/templates/index.html
new file mode 100644
index 00000000000..8d1619a4758
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/todo/templates/index.html
@@ -0,0 +1,30 @@
+{% extends 'base.html' %}
+{% block content %}
+Task List
+Add Task
+
+
+
+ # |
+ Title |
+ Status |
+ Deadline |
+ Actions |
+
+
+
+ {% for task in tasks %}
+
+ {{ loop.index }} |
+ {{ task.title }} |
+ {{ task.status }} |
+ {{ task.deadline }} |
+
+ Edit
+ Delete
+ |
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/example/pythonlib/web/2-todo-flask/todo/templates/task.html b/example/pythonlib/web/2-todo-flask/todo/templates/task.html
new file mode 100644
index 00000000000..672696454a9
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/todo/templates/task.html
@@ -0,0 +1,24 @@
+{% extends 'base.html' %}
+{% block content %}
+{{ title }}
+
+{% endblock %}
diff --git a/example/pythonlib/web/2-todo-flask/todo/test/src/test.py b/example/pythonlib/web/2-todo-flask/todo/test/src/test.py
new file mode 100644
index 00000000000..ab5258cae2a
--- /dev/null
+++ b/example/pythonlib/web/2-todo-flask/todo/test/src/test.py
@@ -0,0 +1 @@
+# Work in Progress...
\ No newline at end of file
diff --git a/example/pythonlib/web/3-hello-django/build.mill b/example/pythonlib/web/3-hello-django/build.mill
new file mode 100644
index 00000000000..fb5158d0f70
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/build.mill
@@ -0,0 +1,31 @@
+package build
+import mill._, pythonlib._
+
+object foo extends PythonModule {
+
+ def mainScript = Task.Source { millSourcePath / "src" / "manage.py" }
+
+ def pythonDeps = Seq("django==5.1.4")
+
+}
+
+/** Usage
+
+> ./mill foo.run test main -v 2 # using inbuilt `django test`, `main` is the app name, `-v 2` is verbosity level 2
+...
+System check identified no issues (0 silenced).
+test_index_view (main.tests.TestScript...)
+Test that the index view returns a 200 status code ... ok
+...
+Ran 1 test...
+OK
+...
+
+> ./mill foo.runBackground runserver
+
+> curl http://localhost:8000
+...Hello, Mill!
...
+
+> ./mill clean foo.runBackground
+
+*/
diff --git a/example/pythonlib/web/3-hello-django/foo/src/app/__init__.py b/example/pythonlib/web/3-hello-django/foo/src/app/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/example/pythonlib/web/3-hello-django/foo/src/app/asgi.py b/example/pythonlib/web/3-hello-django/foo/src/app/asgi.py
new file mode 100644
index 00000000000..df7e978063c
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/app/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for app project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
+
+application = get_asgi_application()
diff --git a/example/pythonlib/web/3-hello-django/foo/src/app/settings.py b/example/pythonlib/web/3-hello-django/foo/src/app/settings.py
new file mode 100644
index 00000000000..3c40a0f9649
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/app/settings.py
@@ -0,0 +1,124 @@
+"""
+Django settings for app project.
+
+Generated by 'django-admin startproject' using Django 5.1.4.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/5.1/ref/settings/
+"""
+
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-7@1hg!(c00%z)t82=^_mu02sxa$nlex_xc!7j++18z5w4dc(iu'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'main',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'app.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'app.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/5.1/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/5.1/howto/static-files/
+
+STATIC_URL = 'static/'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
diff --git a/example/pythonlib/web/3-hello-django/foo/src/app/urls.py b/example/pythonlib/web/3-hello-django/foo/src/app/urls.py
new file mode 100644
index 00000000000..879e54d05df
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/app/urls.py
@@ -0,0 +1,24 @@
+"""
+URL configuration for app project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/5.1/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path
+from main import views
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+ path('', views.index, name="homepage")
+]
diff --git a/example/pythonlib/web/3-hello-django/foo/src/app/wsgi.py b/example/pythonlib/web/3-hello-django/foo/src/app/wsgi.py
new file mode 100644
index 00000000000..829fcc707bb
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/app/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for app project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
+
+application = get_wsgi_application()
diff --git a/example/pythonlib/web/3-hello-django/foo/src/main/__init__.py b/example/pythonlib/web/3-hello-django/foo/src/main/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/example/pythonlib/web/3-hello-django/foo/src/main/admin.py b/example/pythonlib/web/3-hello-django/foo/src/main/admin.py
new file mode 100644
index 00000000000..8c38f3f3dad
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/main/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/example/pythonlib/web/3-hello-django/foo/src/main/apps.py b/example/pythonlib/web/3-hello-django/foo/src/main/apps.py
new file mode 100644
index 00000000000..167f04426e4
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/main/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class MainConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'main'
diff --git a/example/pythonlib/web/3-hello-django/foo/src/main/migrations/__init__.py b/example/pythonlib/web/3-hello-django/foo/src/main/migrations/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/example/pythonlib/web/3-hello-django/foo/src/main/models.py b/example/pythonlib/web/3-hello-django/foo/src/main/models.py
new file mode 100644
index 00000000000..71a83623907
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/main/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/example/pythonlib/web/3-hello-django/foo/src/main/tests.py b/example/pythonlib/web/3-hello-django/foo/src/main/tests.py
new file mode 100644
index 00000000000..aa6ec79bd6a
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/main/tests.py
@@ -0,0 +1,12 @@
+from django.test import TestCase
+from django.urls import reverse
+
+class TestScript(TestCase):
+ def test_index_view(self):
+ """
+ Test that the index view returns a 200 status code
+ and the expected HTML content.
+ """
+ response = self.client.get(reverse('homepage'))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'Hello, Mill!
')
diff --git a/example/pythonlib/web/3-hello-django/foo/src/main/views.py b/example/pythonlib/web/3-hello-django/foo/src/main/views.py
new file mode 100644
index 00000000000..05b8dc7a8f6
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/main/views.py
@@ -0,0 +1,5 @@
+from django.shortcuts import render
+from django.http import HttpResponse
+
+def index(request):
+ return HttpResponse('Hello, Mill!
')
\ No newline at end of file
diff --git a/example/pythonlib/web/3-hello-django/foo/src/manage.py b/example/pythonlib/web/3-hello-django/foo/src/manage.py
new file mode 100755
index 00000000000..49313893cbd
--- /dev/null
+++ b/example/pythonlib/web/3-hello-django/foo/src/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/pythonlib/src/mill/pythonlib/PythonModule.scala b/pythonlib/src/mill/pythonlib/PythonModule.scala
index 9867c0f28e9..c5d247c66a7 100644
--- a/pythonlib/src/mill/pythonlib/PythonModule.scala
+++ b/pythonlib/src/mill/pythonlib/PythonModule.scala
@@ -117,6 +117,7 @@ trait PythonModule extends PipModule with TaskModule { outer =>
*/
def bundleOptions: T[Seq[String]] = Task { Seq("--scie", "eager") }
+
// TODO: right now, any task that calls this helper will have its own python
// cache. This is slow. Look into sharing the cache between tasks.
def runner: Task[PythonModule.Runner] = Task.Anon {
@@ -167,37 +168,6 @@ trait PythonModule extends PipModule with TaskModule { outer =>
)
}
- /**
- * Run the main python script of this module.
- *
- * @see [[mainScript]]
- */
- def runBackground(args: mill.define.Args) = Task.Command {
- val (procUuidPath, procLockfile, procUuid) = mill.scalalib.RunModule.backgroundSetup(T.dest)
-
- Jvm.runSubprocess(
- mainClass = "mill.scalalib.backgroundwrapper.MillBackgroundWrapper",
- classPath = mill.scalalib.ZincWorkerModule.backgroundWrapperClasspath().map(_.path).toSeq,
- jvmArgs = Nil,
- envArgs = runnerEnvTask(),
- mainArgs = Seq(
- procUuidPath.toString,
- procLockfile.toString,
- procUuid,
- "500",
- "",
- pythonExe().path.toString,
- mainScript().path.toString
- ) ++ args.value,
- workingDir = T.workspace,
- background = true,
- useCpPassingJar = false,
- runBackgroundLogToConsole = true,
- javaHome = mill.scalalib.ZincWorkerModule.javaHome().map(_.path)
- )
- ()
- }
-
override def defaultCommandName(): String = "run"
/**
@@ -234,6 +204,37 @@ trait PythonModule extends PipModule with TaskModule { outer =>
PathRef(pexFile)
}
+ /**
+ * Run the main python script of this module.
+ *
+ * @see [[mainScript]]
+ */
+ def runBackground(args: mill.define.Args) = Task.Command {
+ val (procUuidPath, procLockfile, procUuid) = mill.scalalib.RunModule.backgroundSetup(Task.dest)
+
+ Jvm.runSubprocess(
+ mainClass = "mill.scalalib.backgroundwrapper.MillBackgroundWrapper",
+ classPath = mill.scalalib.ZincWorkerModule.backgroundWrapperClasspath().map(_.path).toSeq,
+ jvmArgs = Nil,
+ envArgs = runnerEnvTask(),
+ mainArgs = Seq(
+ procUuidPath.toString,
+ procLockfile.toString,
+ procUuid,
+ "500",
+ "",
+ pythonExe().path.toString,
+ mainScript().path.toString
+ ) ++ args.value,
+ workingDir = Task.workspace,
+ background = true,
+ useCpPassingJar = false,
+ runBackgroundLogToConsole = true,
+ javaHome = mill.scalalib.ZincWorkerModule.javaHome().map(_.path)
+ )
+ ()
+ }
+
trait PythonTests extends PythonModule {
override def moduleDeps: Seq[PythonModule] = Seq(outer)
}