functiontrace 0.5.1__tar.gz → 0.5.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "_functiontrace_rs"
3
3
  version = "0.1.0"
4
- edition = "2021"
4
+ edition = "2024"
5
5
 
6
6
  [dependencies]
7
7
  color-eyre = { version = "0.6", default-features = false }
@@ -10,17 +10,17 @@ functiontrace-server = { version = "0.8.4", default-features = false, features =
10
10
  jiff = { version = "0.2", default-features = false, features = ["std", "serde"] }
11
11
  libc = "0.2"
12
12
  owo-colors = "4.2"
13
- pyo3 = { version = "0.25", features = ["eyre"] }
14
- reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
13
+ pyo3 = { version = "0.29", features = ["eyre"] }
14
+ reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls"] }
15
15
  rmp-serde = "1.3"
16
16
  serde = "1"
17
- toml = "0.9"
17
+ toml = "1.1"
18
18
  tracing = { version = "0.1", features = ["log"] }
19
19
  tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "env-filter", "fmt", "std"] }
20
20
 
21
21
  [build-dependencies]
22
- toml = { version = "0.9", default-features = false, features = ["parse", "serde"] }
23
- pyo3-build-config = { version = "0.25", features = ["resolve-config"] }
22
+ toml = { version = "1.1", default-features = false, features = ["parse", "serde"] }
23
+ pyo3-build-config = { version = "0.29" }
24
24
 
25
25
  [lib]
26
26
  # See https://github.com/PyO3/pyo3 for details
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: functiontrace
3
- Version: 0.5.1
3
+ Version: 0.5.3
4
4
  Summary: A profiling tool that traces your application's execution
5
5
  Author: Alistair Dobke
6
6
  Author-email: Matt Bryant <functiontrace@programsareproofs.com>
@@ -15,7 +15,7 @@ Classifier: Environment :: Console
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
17
  Classifier: Programming Language :: Python :: Implementation :: CPython
18
- Requires-Python: >=3.9
18
+ Requires-Python: >=3.10
19
19
  Description-Content-Type: text/x-rst
20
20
  License-File: LICENSE.md
21
21
  Dynamic: license-file
@@ -28,7 +28,7 @@ Dependencies
28
28
  ------------
29
29
 
30
30
  The FunctionTrace server must be installed for this package to work.
31
- See installation instructions at https://functiontrace.com#installation.
31
+ See installation instructions at https://functiontrace.com/installation/.
32
32
 
33
33
  Usage
34
34
  -----
@@ -6,7 +6,7 @@ Dependencies
6
6
  ------------
7
7
 
8
8
  The FunctionTrace server must be installed for this package to work.
9
- See installation instructions at https://functiontrace.com#installation.
9
+ See installation instructions at https://functiontrace.com/installation/.
10
10
 
11
11
  Usage
12
12
  -----
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: functiontrace
3
- Version: 0.5.1
3
+ Version: 0.5.3
4
4
  Summary: A profiling tool that traces your application's execution
5
5
  Author: Alistair Dobke
6
6
  Author-email: Matt Bryant <functiontrace@programsareproofs.com>
@@ -15,7 +15,7 @@ Classifier: Environment :: Console
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
17
  Classifier: Programming Language :: Python :: Implementation :: CPython
18
- Requires-Python: >=3.9
18
+ Requires-Python: >=3.10
19
19
  Description-Content-Type: text/x-rst
20
20
  License-File: LICENSE.md
21
21
  Dynamic: license-file
@@ -28,7 +28,7 @@ Dependencies
28
28
  ------------
29
29
 
30
30
  The FunctionTrace server must be installed for this package to work.
31
- See installation instructions at https://functiontrace.com#installation.
31
+ See installation instructions at https://functiontrace.com/installation/.
32
32
 
33
33
  Usage
34
34
  -----
@@ -11,4 +11,5 @@ functiontrace.egg-info/SOURCES.txt
11
11
  functiontrace.egg-info/dependency_links.txt
12
12
  functiontrace.egg-info/entry_points.txt
13
13
  functiontrace.egg-info/top_level.txt
14
- rust/lib.rs
14
+ rust/lib.rs
15
+ rust/multiprocessing.sh.tpl
@@ -19,7 +19,12 @@ exec $(which {python}) -m functiontrace "$@"
19
19
 
20
20
 
21
21
  def setup_dependencies():
22
- # Generate a temp directory to store our wrappers in. We'll temporarily
22
+ if f"{tempfile.gettempdir()}/py-functiontrace" in os.environ["PATH"]:
23
+ # We've already set up dependencies. This only needs to happen once, since otherwise we'll
24
+ # just make a bunch of useless temp directories.
25
+ return
26
+
27
+ # Generate a temp directory to store our wrappers in. We'll temporarily
23
28
  # add this directory to our path.
24
29
  tempdir = tempfile.mkdtemp(prefix="py-functiontrace")
25
30
  os.environ["PATH"] = tempdir + os.pathsep + os.environ["PATH"]
@@ -89,10 +94,19 @@ def main():
89
94
  # below, which needs to operate in the context of our new module.
90
95
  # NOTE: We use SourceFileLoader, rather than allowing importlib to infer
91
96
  # the proper loader, in order to support files without a .py extension.
92
- spec = util.spec_from_loader(
93
- "__main__", machinery.SourceFileLoader("__main__", target_file)
97
+ mod = util.module_from_spec(
98
+ util.spec_from_loader(
99
+ "__main__", machinery.SourceFileLoader("__main__", target_file)
100
+ )
94
101
  )
95
- mod = util.module_from_spec(spec)
102
+
103
+ # Clear __spec__ to match Python's standard `python foo.py` loading
104
+ mod.__spec__ = None
105
+ # Record the file we're using so `multiprocessing` can load it (in a non-forked context).
106
+ # Without this, `multiprocessing` in `forkserver` contexts is asked to load functions that
107
+ # it can't find.
108
+ mod.__file__ = target_file
109
+
96
110
  sys.modules.update({"__main__": mod})
97
111
 
98
112
  # Ensure we're setup to be able to run.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "functiontrace"
3
- version = "0.5.1"
3
+ version = "0.5.3"
4
4
  description = "A profiling tool that traces your application's execution"
5
5
  authors = [
6
6
  { name = "Matt Bryant", email = "functiontrace@programsareproofs.com"},
@@ -9,13 +9,13 @@ authors = [
9
9
  license = "LicenseRef-Prosperity-Public-License-3.0.0"
10
10
  license-files = ["LICENSE.md"]
11
11
  readme = "README.rst"
12
- # NOTE: Actively tested on Python 3.9 - 3.13 and may plausibly break on greater
13
- # versions due to C extension hacks. However, setting a maximum version causes
14
- # new Python versions to simply install very old versions of Functiontrace that
15
- # didn't have the `requires-python` annotation on them and are also unlikely to
16
- # work. Best to only set the minimum and let bug reports notify us of issues
17
- # with new Python releases.
18
- requires-python = ">=3.9"
12
+ # NOTE: Actively tested on Python 3.10 - 3.14 and may plausibly break on
13
+ # greater versions due to C extension hacks. However, setting a maximum version
14
+ # causes new Python versions to simply install very old versions of
15
+ # Functiontrace that didn't have the `requires-python` annotation on them and
16
+ # are also unlikely to work. Best to only set the minimum and let bug reports
17
+ # notify us of issues with new Python releases.
18
+ requires-python = ">=3.10"
19
19
  keywords = [
20
20
  "profiler",
21
21
  "debugger"
@@ -7,7 +7,7 @@ mod _functiontrace_rs {
7
7
  use serde::{Deserialize, Serialize};
8
8
  use std::borrow::Cow;
9
9
  use std::cell::Cell;
10
- use std::ffi::{CStr, OsString, c_void};
10
+ use std::ffi::{CStr, OsString};
11
11
  use std::io::Write;
12
12
  use std::os::unix::net::UnixStream;
13
13
  use std::sync::atomic::{AtomicBool, Ordering};
@@ -148,7 +148,7 @@ mod _functiontrace_rs {
148
148
  let state = THREAD_STATE.get();
149
149
 
150
150
  if state.is_null() {
151
- // We shouldn't be asked to reset at thread that hasn't yet been registered.
151
+ // We shouldn't be asked to reset a thread that hasn't yet been registered.
152
152
  return None;
153
153
  }
154
154
 
@@ -206,7 +206,7 @@ mod _functiontrace_rs {
206
206
  .wrap_err("Failed to convert MIN_PYTHON_VERSION to int")?,
207
207
  )
208
208
  .wrap_err("Failed to expose _min_python_version")?;
209
- m.add("_max_python_version", 13)
209
+ m.add("_max_python_version", 14)
210
210
  .wrap_err("Failed to expose _max_python_version")?;
211
211
 
212
212
  color_eyre::install().wrap_err("Failed to install color_eyre")?;
@@ -241,6 +241,8 @@ mod _functiontrace_rs {
241
241
  module: &Bound<'_, PyModule>,
242
242
  output_directory: OsString,
243
243
  ) -> Result<()> {
244
+ use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
245
+
244
246
  // The name of the env var we use to notify subprocesses that there's a trace in progress.
245
247
  const BREADCRUMB: &str = "FUNCTIONTRACE_LIVE";
246
248
 
@@ -259,20 +261,20 @@ mod _functiontrace_rs {
259
261
  // Parse args out of `sys.argv`, since it excludes the Python executable and shows the
260
262
  // script's path first. This is much more complicated than using `std::env::args`, but
261
263
  // is more practical.
262
- Python::with_gil(|py| -> Result<String> {
264
+ Python::attach(|py| -> Result<String> {
263
265
  let argv = py
264
266
  .import("sys")
265
267
  .and_then(|sys| sys.getattr("argv"))
266
268
  .wrap_err("Failed to access sys.argv")?;
267
269
 
268
270
  let args = argv
269
- .downcast::<pyo3::types::PyList>()
271
+ .cast::<pyo3::types::PyList>()
270
272
  .map_err(|e| eyre!("Failed to read sys.argv as list: {}", e))?;
271
273
 
272
274
  let mut full_args = args.iter().fold(String::new(), |s, arg| {
273
275
  // Fetch the next arg as a string if possible.
274
276
  let arg = arg
275
- .downcast::<pyo3::types::PyString>()
277
+ .cast::<pyo3::types::PyString>()
276
278
  .map_err(|e| eyre!("Failed to read sys.argv[x] as string: {}", e))
277
279
  .and_then(|arg| {
278
280
  arg.to_str()
@@ -327,7 +329,7 @@ mod _functiontrace_rs {
327
329
  Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
328
330
  eprintln!("Unable to find `functiontrace-server` in the current $PATH.");
329
331
  eprintln!(
330
- "See https://functiontrace.com#installation for installation instructions."
332
+ "See https://functiontrace.com/installation/ for installation instructions."
331
333
  );
332
334
 
333
335
  // Exit here rather than returning an error, since that'll emit a confusing
@@ -337,15 +339,24 @@ mod _functiontrace_rs {
337
339
  e => e.wrap_err("Failed to launch functiontrace-server")?,
338
340
  };
339
341
 
340
- format!("/tmp/functiontrace-server.sock.{}", server.id()).into()
342
+ let sockaddr = format!("/tmp/functiontrace-server.sock.{}", server.id());
343
+
344
+ // Also allocate a directory for storing wrappers (multiprocessing.set_executable,
345
+ // etc) we may need.
346
+ std::fs::DirBuilder::new()
347
+ .recursive(true)
348
+ .mode(0o700)
349
+ .create(format!("{sockaddr}.wrappers"))
350
+ .wrap_err("Failed to create wrapper directory")?;
351
+ sockaddr.into()
341
352
  };
342
353
 
343
- Python::with_gil(|_| {
354
+ Python::attach(|_| {
344
355
  // Register an env var so subprocesses know how to connect to our
345
356
  // profile.
346
357
  unsafe {
347
358
  // SAFETY: `set_var` can only be done while no one else is reading or writing to the
348
- // environment. We're holding the GIL, so this _should_ be true, but there's no real
359
+ // environment. We're holding the GIL, so this _should_ be true, but there's no real
349
360
  // way to enforce this.
350
361
  //
351
362
  // This doesn't seem to blow up in C even when we don't have the GIL, so
@@ -362,7 +373,7 @@ mod _functiontrace_rs {
362
373
  // Send an initialization message to the server with some information about us. This will
363
374
  // only be sent once, and all future threads/subprocesses will be associated underneath us.
364
375
  let init = {
365
- let python_version = Python::with_gil(|py| {
376
+ let python_version = Python::attach(|py| {
366
377
  let version = py.version_info();
367
378
 
368
379
  format!(
@@ -371,14 +382,14 @@ mod _functiontrace_rs {
371
382
  )
372
383
  });
373
384
 
374
- let platform = Python::with_gil(|py| -> Result<String> {
385
+ let platform = Python::attach(|py| -> Result<String> {
375
386
  let platform = py
376
387
  .import("sys")
377
388
  .and_then(|sys| sys.getattr("platform"))
378
389
  .wrap_err("Failed to access sys.platform")?;
379
390
 
380
391
  Ok(platform
381
- .downcast::<pyo3::types::PyString>()
392
+ .cast::<pyo3::types::PyString>()
382
393
  .map_err(|e| eyre!("Failed to read sys.platform as string: {}", e))?
383
394
  .to_str()
384
395
  .wrap_err("Failed to read Rust string out of PyString")?
@@ -414,6 +425,61 @@ mod _functiontrace_rs {
414
425
  .wrap_err("Failed to close initialization socket")?;
415
426
  }
416
427
 
428
+ // `multiprocessing` uses an absolute path to an executable to launch forkserver workers
429
+ // using `-c` for the actual code,
430
+ // Override this with our own wrapper that also sets up functiontrace.
431
+ Python::attach(|py| -> Result<()> {
432
+ let executable: String = py.import("sys")?.getattr("executable")?.extract()?;
433
+ let wrapper = {
434
+ // We'll store the wrappers next to the socket.
435
+ // TODO: Ideally we'd have these all in the same tmpdir, but that requires changing
436
+ // client and server code so would be a breaking change.
437
+ let mut directory = SOCKADDR
438
+ .get()
439
+ .expect("sockaddr is set during server initialization")
440
+ .clone();
441
+ directory.push(".wrappers");
442
+
443
+ std::path::PathBuf::from(directory)
444
+ .join("multiprocessing")
445
+ .join(executable.strip_prefix("/").unwrap_or(&executable))
446
+ };
447
+
448
+ // Create a multiprocessing wrapper for the current executable if it doesn't exist yet.
449
+ std::fs::create_dir_all(
450
+ wrapper
451
+ .parent()
452
+ .expect("Wrappers are always in a subdirectory"),
453
+ )
454
+ .wrap_err("Failed to create multiprocessing wrapper directories")?;
455
+
456
+ match std::fs::OpenOptions::new()
457
+ .write(true)
458
+ .create_new(true)
459
+ .open(&wrapper)
460
+ {
461
+ Ok(mut file) => {
462
+ let script = include_str!("multiprocessing.sh.tpl").replace(
463
+ "@PYTHON_INTERPRETER@",
464
+ // NOTE: This is equivalent to shlex.quote
465
+ &format!("'{}'", executable.replace('\'', "'\"'\"'")),
466
+ );
467
+
468
+ file.write_all(script.as_bytes())
469
+ .wrap_err("Failed to write multiprocessing wrapper")?;
470
+ file.set_permissions(std::fs::Permissions::from_mode(0o700))
471
+ .wrap_err("Failed to `chmod +x` multiprocessing wrapper")?;
472
+ }
473
+ Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {}
474
+ Err(err) => return Err(err).wrap_err("Failed to create multiprocessing wrapper"),
475
+ }
476
+ py.import("multiprocessing")?
477
+ .getattr("set_executable")?
478
+ .call1((wrapper,))?;
479
+ Ok(())
480
+ })
481
+ .wrap_err("Failed to configure multiprocessing tracing")?;
482
+
417
483
  ////////////////////////////////////////////////////////////////////////////
418
484
  // Teardown Configuration
419
485
  ////////////////////////////////////////////////////////////////////////////
@@ -433,7 +499,7 @@ mod _functiontrace_rs {
433
499
  //
434
500
  // NOTE: We specifically do this rather than `Py_AtExit` since we want to run before all
435
501
  // the Python teardown occurs.
436
- Python::with_gil(|py| -> Result<()> {
502
+ Python::attach(|py| -> Result<()> {
437
503
  py.import("atexit")
438
504
  .and_then(|atexit| atexit.getattr("register"))
439
505
  .wrap_err("Failed to access atexit.register")?
@@ -460,12 +526,13 @@ mod _functiontrace_rs {
460
526
 
461
527
  // Register our tracing functions with Python - both the normal one and the multithreaded
462
528
  // setup trampoline.
529
+ // TODO: PyEval_SetProfileAllThreads in >=3.12 should simplify this.
463
530
  unsafe {
464
531
  // SAFETY: both parameters are of the proper type.
465
532
  pyo3::ffi::PyEval_SetProfile(Some(functiontrace), std::ptr::null_mut())
466
533
  };
467
534
 
468
- Python::with_gil(|py| -> Result<()> {
535
+ Python::attach(|py| -> Result<()> {
469
536
  py.import("threading")
470
537
  .and_then(|threading| threading.getattr("setprofile"))
471
538
  .wrap_err("Failed to access threading.setprofile")?
@@ -561,23 +628,6 @@ mod _functiontrace_rs {
561
628
  Ok(())
562
629
  }
563
630
 
564
- #[unsafe(no_mangle)]
565
- extern "C" fn c_thread_teardown(state: *mut c_void) {
566
- if state.is_null() {
567
- // We never fully initialized this thread, so skip teardown.
568
- return;
569
- }
570
-
571
- let writer = unsafe {
572
- // SAFETY: writer is a valid pointer to an initialized `ThreadState`.
573
- &mut (state as *mut ThreadState)
574
- .as_mut()
575
- .expect("state is non-null")
576
- };
577
-
578
- thread_teardown(writer).expect("Failed to tear down FunctionTrace thread")
579
- }
580
-
581
631
  /// Tear down the given thread by flushing any outstanding messages.
582
632
  ///
583
633
  /// This is called when the thread has shut down, including in multithread/process situations.
@@ -636,7 +686,11 @@ mod _functiontrace_rs {
636
686
  /// thread state, before replacing itself with the normal Fprofile_FunctionTrace handler.
637
687
  #[pyfunction]
638
688
  #[pyo3(name = "_thread_trace_trampoline")]
639
- fn thread_trace_trampoline(_frame: PyObject, _event: PyObject, _arg: PyObject) -> Result<()> {
689
+ fn thread_trace_trampoline(
690
+ _frame: Py<PyAny>,
691
+ _event: Py<PyAny>,
692
+ _arg: Py<PyAny>,
693
+ ) -> Result<()> {
640
694
  // Register the current thread for tracing
641
695
  thread_register().wrap_err("Failed to register new FunctionTrace thread")?;
642
696
 
@@ -787,7 +841,7 @@ mod _functiontrace_rs {
787
841
  pyo3::ffi::PyFrame_GetCode(frame) as *mut pyo3::ffi::PyObject
788
842
  };
789
843
 
790
- Python::with_gil(|py| {
844
+ Python::attach(|py| {
791
845
  let code = unsafe {
792
846
  // SAFETY: code is guaranteed to be a valid PyCodeObject since it
793
847
  // came from `PyFrame_GetCode`.
@@ -799,7 +853,7 @@ mod _functiontrace_rs {
799
853
  let unicode_converter = |pyobj: PyResult<Bound<'_, PyAny>>| {
800
854
  pyobj
801
855
  .map(|obj| {
802
- if let Ok(filename) = obj.downcast::<pyo3::types::PyString>() {
856
+ if let Ok(filename) = obj.cast::<pyo3::types::PyString>() {
803
857
  Cow::from(filename.to_string_lossy().into_owned())
804
858
  } else {
805
859
  Cow::Borrowed("<UNKNOWN>")
@@ -856,13 +910,13 @@ mod _functiontrace_rs {
856
910
  if !func.m_module.is_null() {
857
911
  // NOTE: _Typically_ `m_module` is a PyModule, but there are no requirements on
858
912
  // what type it needs to be! In practice, we only see PyModules and PyStrings.
859
- Python::with_gil(|py| {
913
+ Python::attach(|py| {
860
914
  let m_module = unsafe {
861
915
  // SAFETY: m_module is a non-NULL pointer to a valid Python object.
862
916
  Bound::from_borrowed_ptr(py, func.m_module)
863
917
  };
864
918
 
865
- if let Ok(module) = m_module.downcast::<pyo3::types::PyModule>() {
919
+ if let Ok(module) = m_module.cast::<pyo3::types::PyModule>() {
866
920
  // This is a PyModule, so get its name if possible.
867
921
  let name = module.name();
868
922
  let module_name = if let Ok(name) = &name {
@@ -879,7 +933,7 @@ mod _functiontrace_rs {
879
933
  .serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
880
934
  } else {
881
935
  let module_name =
882
- if let Ok(module) = m_module.downcast::<pyo3::types::PyString>() {
936
+ if let Ok(module) = m_module.cast::<pyo3::types::PyString>() {
883
937
  module.to_cow().unwrap_or(Cow::Borrowed("<invalid string>"))
884
938
  } else {
885
939
  if let Ok(typ) = m_module.get_type().qualname().map(|x| x.to_string()) {
@@ -1125,7 +1179,7 @@ mod _functiontrace_rs {
1125
1179
  if let Some(result) = parser(&input) {
1126
1180
  return Ok(result);
1127
1181
  }
1128
- Python::with_gil(|py| {
1182
+ Python::attach(|py| {
1129
1183
  // We need to check with the Python interpreter during loops, otherwise we
1130
1184
  // might not receive ctrl-c events.
1131
1185
  py.check_signals()
@@ -1507,7 +1561,7 @@ mod hooks {
1507
1561
  /// function, then attaching enough information about the original function to call it.
1508
1562
  pub fn install() -> Result<()> {
1509
1563
  for hook in HOOKS.iter() {
1510
- Python::with_gil(|py| -> Result<()> {
1564
+ Python::attach(|py| -> Result<()> {
1511
1565
  let (module_name, method_name) = hook
1512
1566
  .target
1513
1567
  .rsplit_once(".")
@@ -1649,14 +1703,14 @@ mod hooks {
1649
1703
  .get()
1650
1704
  .expect("Wrapper functions can't be called until hooked");
1651
1705
 
1652
- let proxied = Python::with_gil(|py| -> Result<()> {
1706
+ let proxied = Python::attach(|py| -> Result<()> {
1653
1707
  if let Some(mut writer) = crate::_functiontrace_rs::thread_state() {
1654
1708
  let args = unsafe {
1655
1709
  // SAFETY: `args` represents a valid argument tuple
1656
1710
  Bound::from_borrowed_ptr(py, args)
1657
1711
  };
1658
1712
  let args = args
1659
- .downcast()
1713
+ .cast()
1660
1714
  .map_err(Into::<PyErr>::into)
1661
1715
  .wrap_err("Python args must be a tuple")?;
1662
1716
 
@@ -1733,7 +1787,7 @@ mod hooks {
1733
1787
  // `import` takes rather complicated arguments, but in practice we only care about
1734
1788
  // the first argument (name)
1735
1789
  let module = if let Ok(arg) = args.get_item(0) {
1736
- arg.downcast()
1790
+ arg.cast()
1737
1791
  .map_err(Into::into)
1738
1792
  .and_then(|module| module.to_str())
1739
1793
  .map(|module| Cow::Owned(module.to_string()))
@@ -0,0 +1,53 @@
1
+ #!/bin/sh
2
+
3
+ # Multiprocessing passes the code to execute in workers as `-c <code>`.
4
+ # Strip that out and run tracing setup first, maintaining the order of arguments just in case.
5
+
6
+ remaining=$#
7
+ while [ "$remaining" -gt 0 ]; do
8
+ arg=$1
9
+ shift
10
+ remaining=$((remaining - 1))
11
+ case "$arg" in
12
+ -W|-X)
13
+ # These arguments can consume other arguments. Skip them so we don't parse the wrong `-c`.
14
+ value=$1
15
+ shift
16
+ remaining=$((remaining - 1))
17
+ set -- "$@" "$arg" "$value"
18
+ ;;
19
+ -c)
20
+ code=$1
21
+ shift
22
+ remaining=$((remaining - 1))
23
+ set -- "$@" -c '
24
+ exec(
25
+ (
26
+ compile(
27
+ __import__("sys").argv.pop(1),
28
+ "<string>",
29
+ "exec",
30
+ dont_inherit=True,
31
+ ),
32
+ # Start tracing before exec runs the compiled code.
33
+ __import__("_functiontrace_rs").begin_tracing("."),
34
+ )[0]
35
+ )
36
+ ' "$code"
37
+ break
38
+ ;;
39
+ *)
40
+ set -- "$@" "$arg"
41
+ ;;
42
+ esac
43
+ done
44
+
45
+ # Consume the rest of the args without looking.
46
+ while [ "$remaining" -gt 0 ]; do
47
+ arg=$1
48
+ shift
49
+ remaining=$((remaining - 1))
50
+ set -- "$@" "$arg"
51
+ done
52
+
53
+ exec @PYTHON_INTERPRETER@ "$@"
File without changes
File without changes
File without changes
File without changes
File without changes